package com.sc.nft.dao;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sc.nft.annotation.PartitionField;
import com.sc.nft.annotation.PartitionPK;
import com.sc.nft.annotation.PartitionTable;
import com.sc.nft.entity.CollectionChainInitDetailsLog;
import com.sc.nft.entity.vo.TmpCollectionVO;
import com.sc.nft.enums.CollectionChainDealStatusEnum;
import com.sc.nft.enums.UserChainLogStatusEnum;
import com.sc.nft.helper.TableIdGenerator;
import com.sc.nft.helper.TablePartitionExecutor;
import com.sc.nft.helper.TablePartitionRouter;
import com.sc.nft.mapper.CollectionChainInitDetailsLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 藏品初始化上链记录详情表(sc_collection_chain_init_details_log)数据DAO
 *
 * @author Sun
 * @description 由 Mybatisplus Code Generator 创建
 * @since 2023-04-18 14:28:42
 */
@Slf4j
@Repository
@RequiredArgsConstructor
public class CollectionChainInitDetailsLogDaoImpl
        extends ServiceImpl<CollectionChainInitDetailsLogMapper, CollectionChainInitDetailsLog>
        implements CollectionChainInitDetailsLogDao {

    private final TableIdGenerator generator;

    public void insertBatch(List<CollectionChainInitDetailsLog> list) {
        // 根据路由分批插入
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        TablePartitionRouter.Router router = TablePartitionRouter.ofCollectionChainInitDetailsLogRouter();

        // 按 collectionId 分组
        list.stream()
                .filter(Objects::nonNull)
                .filter(item -> item.getCollectionId() != null)
                .collect(Collectors.groupingBy(item -> router.routeWithField(item.getCollectionId())))
                .forEach((key, value) -> TablePartitionExecutor.exec(key, (partition) -> {
                    // 生成并填充ID列表
                    generator.fillAutoIncrementIds(partition, value, this::nextIds, CollectionChainInitDetailsLog::setId);
                    // 批量插入
                    insertBatch0(value);
                }));
    }

    private void insertBatch0(List<CollectionChainInitDetailsLog> list) {
        baseMapper.insetBatch(list);
    }

    private List<Long> nextIds(String partition, int count) {
        return generator.defaultNextIds(TABLE, partition, count);
    }

    public List<TmpCollectionVO> getTmpData() {
        return baseMapper.getTmpData();
    }

    @PartitionTable(TABLE)
    public List<CollectionChainInitDetailsLog> getListByStatusAndDealStatusAndCollectionId(
            @PartitionField Long collectionId, UserChainLogStatusEnum userChainLogStatusEnum, CollectionChainDealStatusEnum collectionChainDealStatusEnum) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, userChainLogStatusEnum);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, collectionChainDealStatusEnum);
        wrapper.last("limit 150");
        return list(wrapper);
    }


    @PartitionTable(TABLE)
    public List<Long> getWithTransferQueue(@PartitionField Long collectionId) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(CollectionChainInitDetailsLog::getId);
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE);
        wrapper.last("order by rand() limit 3000");
        return listObjs(wrapper, x -> (Long) x);
    }

    public Long getOneTypeIds(UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus) {
        // 因无路由键，这里采取遍历所有分片方法来实现
        return TablePartitionRouter.ofCollectionChainInitDetailsLogRouter().parts()
                .parallelStream()
                .map(pt -> TablePartitionExecutor.exec(pt, () -> getOneTypeIds0(status, dealStatus)))
                .filter(Objects::nonNull)
                .findFirst().orElse(null);
    }

    private Long getOneTypeIds0(UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus) {
        return baseMapper.getOneTypeIds(status.getCode(), dealStatus.getCode());
    }

    @PartitionTable(TABLE)
    public Integer getCountByStatusAndChainStatusAndCollectionId(
            UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus, @PartitionField Long collectionId) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        wrapper.gt(CollectionChainInitDetailsLog::getTokenId, 0);
        return count(wrapper);
    }

    public Boolean updateContractAddressAndTransactionHashAndStatusAndDealStatusByIds(
            String contractAddress, String transactionHash, UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus, List<Long> ids) {

        TablePartitionRouter.Router router = TablePartitionRouter.ofCollectionChainInitDetailsLogRouter();

        // 按 id 分组
        return ids.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.groupingBy(router::routeWithPk))
                .entrySet()
                .stream()
                .map(entry -> TablePartitionExecutor.exec(entry.getKey(), (partition) -> {
                    log.debug("批量更新 partition: {}, ids: {}", partition, entry.getValue());
                    return updateContractAddressAndTransactionHashAndStatusAndDealStatusByIds0(contractAddress, transactionHash, status, dealStatus, entry.getValue());
                }))
                .allMatch(BooleanUtil::isTrue);
    }

    private Boolean updateContractAddressAndTransactionHashAndStatusAndDealStatusByIds0(
            String contractAddress, String transactionHash, UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus, List<Long> ids) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.in(CollectionChainInitDetailsLog::getId, ids);
        wrapper.set(CollectionChainInitDetailsLog::getContractAddress, contractAddress);
        wrapper.set(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        wrapper.set(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        return update(wrapper);
    }


    public Boolean updateDealStatusByIds(CollectionChainDealStatusEnum dealStatus, List<Long> ids) {
        TablePartitionRouter.Router router = TablePartitionRouter.ofCollectionChainInitDetailsLogRouter();
        // 按 id 分组
        return ids.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.groupingBy(router::routeWithPk))
                .entrySet()
                .stream()
                .map(entry -> TablePartitionExecutor.exec(entry.getKey(), (partition) -> {
                    log.debug("批量更新 partition: {}, ids: {}", partition, entry.getValue());
                    return updateDealStatusByIds0(dealStatus, entry.getValue());
                }))
                .allMatch(BooleanUtil::isTrue);
    }

    private Boolean updateDealStatusByIds0(CollectionChainDealStatusEnum dealStatus, List<Long> ids) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.in(CollectionChainInitDetailsLog::getId, ids);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        return update(wrapper);
    }


    public CollectionChainInitDetailsLog getOneByTransactionHash(String transactionHash) {
        // 因无路由键，这里采取遍历所有分片方法来实现
        return TablePartitionRouter.ofCollectionChainInitDetailsLogRouter().parts()
                .parallelStream()
                .map(pt -> TablePartitionExecutor.exec(pt, () -> getOneByTransactionHash0(transactionHash)))
                .filter(Objects::nonNull)
                .findFirst().orElse(null);
    }

    private CollectionChainInitDetailsLog getOneByTransactionHash0(String transactionHash) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.IN_CHINA);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.IN_CHINA);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, 0);
        wrapper.last("limit 1");
        return getOne(wrapper);
    }

    @PartitionTable(TABLE)
    public Boolean updateTokenIdAndNumberingById(@PartitionPK Long id, Integer tokenId) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getId, id);
        wrapper.set(CollectionChainInitDetailsLog::getTokenId, tokenId);
        wrapper.set(CollectionChainInitDetailsLog::getNumbering, tokenId);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE);
        wrapper.set(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        return update(wrapper);
    }

    @PartitionTable(TABLE)
    public Boolean updateContractAddressAndTransactionHashAndStatusAndDealStatusById(
            String contractAddress, String transactionHash, UserChainLogStatusEnum status,
            CollectionChainDealStatusEnum dealStatus, @PartitionPK Long id) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getId, id);
        wrapper.set(CollectionChainInitDetailsLog::getContractAddress, contractAddress);
        wrapper.set(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        wrapper.set(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        return update(wrapper);
    }

    @PartitionTable(TABLE)
    public CollectionChainInitDetailsLog getOneByCollectionIdAndUuid(@PartitionField Long collectionId, String collectionUuid) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionUuid, collectionUuid);
        return getOne(wrapper);
    }

    @PartitionTable(TABLE)
    public List<CollectionChainInitDetailsLog> getListByCollectionId(@PartitionField Long collectionId) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        return list(wrapper);
    }

    @PartitionTable(TABLE)
    public List<CollectionChainInitDetailsLog> getPageListByCollectionIdAndPage(@PartitionField Long collectionId, int pageNo, int pageSize) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.last("limit " + StrUtil.format("{},{}", pageNo, pageSize));
        return list(wrapper);
    }

    public List<CollectionChainInitDetailsLog> getListByTransactionHash(String transactionHash) {
        // 因无路由键，这里采取遍历所有分片方法来实现
        return TablePartitionRouter.ofCollectionChainInitDetailsLogRouter().parts()
                .parallelStream()
                .map(part -> TablePartitionExecutor.exec(part, () -> getListByTransactionHash0(transactionHash)))
                .filter(CollUtil::isNotEmpty)
                .findFirst().orElse(new ArrayList<>());
    }

    private List<CollectionChainInitDetailsLog> getListByTransactionHash0(String transactionHash) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        return list(wrapper);
    }

    @PartitionTable(TABLE)
    public CollectionChainInitDetailsLog getOneByTokenIdAndCollectionId(Integer numbering, @PartitionField Long collectionId) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, numbering);
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE);
        return getOne(wrapper);
    }

    @PartitionTable(TABLE)
    public Boolean updateDealStatusById(@PartitionPK Long id, CollectionChainDealStatusEnum dealStatus) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getId, id);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        return update(wrapper);
    }

    @PartitionTable(TABLE)
    public List<CollectionChainInitDetailsLog> metaChainInitListAndCollectionIdAndLimit(
            @PartitionField Long collectionId, UserChainLogStatusEnum status, CollectionChainDealStatusEnum dealStatus, int size) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        wrapper.last("limit " + size);
        return list(wrapper);
    }

    @PartitionTable(TABLE)
    public boolean updateDealStatusAndTransactionHashByCollectionIdAndNumbering(
            @PartitionField Long collectionId, String transactionHash, Integer tokenId) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, tokenId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEDING);

        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE);
        wrapper.set(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        wrapper.set(CollectionChainInitDetailsLog::getModifyTime, DateTime.now());
        return update(wrapper);
    }

    @PartitionTable(TABLE)
    public boolean updateDealStatusAndTransactionHashByCollectionIdAndNumbering(
            @PartitionField Long collectionId, String transactionHash, Integer tokenId, CollectionChainDealStatusEnum dealStatus) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, tokenId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEDING);

        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        wrapper.set(CollectionChainInitDetailsLog::getTransactionHash, transactionHash);
        wrapper.set(CollectionChainInitDetailsLog::getModifyTime, DateTime.now());
        return update(wrapper);
    }

    @PartitionTable(TABLE)
    public CollectionChainInitDetailsLog getOneByCollectionIdAnTokenId(@PartitionField Long collectionId, Integer numbering) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, numbering);
        return getOne(wrapper);
    }

    @PartitionTable(TABLE)
    public CollectionChainInitDetailsLog getOneByCollectionIdAnTokenIdAndStatsAndDealStatus(
            @PartitionField Long collectionId, Integer tokenId, UserChainLogStatusEnum status) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, tokenId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.in(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE, CollectionChainDealStatusEnum.FROZEN);
        return getOne(wrapper);
    }

    @PartitionTable(TABLE)
    public Boolean getOneByCollectionIdsAnTokenIdAndStatsAndDealStatus(
            @PartitionField Long collectionId, List<Integer> tokenIds, UserChainLogStatusEnum status) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, status);
        wrapper.in(CollectionChainInitDetailsLog::getTokenId, tokenIds);
        wrapper.in(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE, CollectionChainDealStatusEnum.FROZEN);
        return count(wrapper) == tokenIds.size();
    }

    @PartitionTable(TABLE)
    public void updateDealStatusByCollectionIdAndTokenId(@PartitionField Long collectionId, Integer tokenId, CollectionChainDealStatusEnum dealStatus) {
        LambdaUpdateWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getTokenId, tokenId);
        wrapper.set(CollectionChainInitDetailsLog::getDealStatus, dealStatus);
        update(wrapper);
    }

    @Override
    @PartitionTable(TABLE)
    public CollectionChainInitDetailsLog getById(@PartitionPK Long id) {
        return super.getById(id);
    }

    @Override
    @PartitionTable(TABLE)
    public boolean updateById(@PartitionPK CollectionChainInitDetailsLog model) {
        return super.updateById(model);
    }


    @Override
    @PartitionTable(TABLE)
    public List<CollectionChainInitDetailsLog> getExcludeListByCollectionId(@PartitionField Long collectionId, List<Long> list, Integer limit) {
        LambdaQueryWrapper<CollectionChainInitDetailsLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CollectionChainInitDetailsLog::getCollectionId, collectionId);
        wrapper.eq(CollectionChainInitDetailsLog::getStatus, UserChainLogStatusEnum.SUCCESS);
        wrapper.eq(CollectionChainInitDetailsLog::getDealStatus, CollectionChainDealStatusEnum.TRADEABLE);
        wrapper.notIn(!CollectionUtils.isEmpty(list), CollectionChainInitDetailsLog::getId, list);
        wrapper.last("order by rand()");
        wrapper.last("limit " + limit);
        return list(wrapper);
    }

    public static class CollectionChainInitDetailsLogRouter implements TablePartitionRouter.Router {

        private final JdbcTemplate jdbcTemplate;
        private final int partitions; // 分区数量
        private final ConcurrentMap<Long, Integer> partitionMap = new ConcurrentHashMap<>();

        public CollectionChainInitDetailsLogRouter(ApplicationContext context) {
            this.jdbcTemplate = context.getBean(JdbcTemplate.class);
            this.partitions = context.getEnvironment()
                    .getProperty("app.partition.sc_collection_chain_init_details_log.count", Integer.class, 2);
            for (int i = 0; i < partitions; i++) {
                List<Long> collectionIds = findCollectionIds(i);
                log.info("[{}]初始化分区设置 partition: {}, collectionIds: {}", TABLE, i, collectionIds);
                for (Long cid : collectionIds) {
                    partitionMap.put(cid, i);
                }
            }
        }

        /**
         * sc_collection_chain_init_details_log
         * metago_partition.sc_collection_chain_init_details_log_01
         * metago_partition.sc_collection_chain_init_details_log_02
         * ... ...
         *
         * @param partition 分片索引
         * @return 分片表中 collectionIds 列表
         */
        private List<Long> findCollectionIds(int partition) {
            String table = TABLE;
            if (partition > 0) {
                table = String.format("metago_partition.%s_%02d", table, partition);
            }
            return jdbcTemplate.queryForList(String.format("SELECT DISTINCT collection_id FROM %s", table), Long.class);
        }

        @Override
        public String routeWithPk(Number pk) {

            // 新数据有分片前缀
            String val = pk.toString();

            // 历史数据无分片前缀
            if (val.length() < 17) {
                return "";
            }

            // 单位分片
            if (val.length() == 17) {
                return "0" + val.charAt(0);
            }

            // 双位分片
            return val.substring(0, 2);
        }

        @Override
        public String routeWithField(Number collectionId) {
            Integer partition = partitionMap.get(collectionId.longValue());
            // 未命中则走最新分片
            if (partition == null) {
                return String.format("%02d", partitions - 1);
            }
            if (partition.equals(0)) {
                return "";
            }
            return String.format("%02d", partition);
        }

        @Override
        public List<String> parts() {
            // 第 0 片没有后缀（避免旧表数据迁移）
            return IntStream.range(0, partitions)
                    .mapToObj(i -> i == 0 ? "" : String.format("%02d", i))
                    .collect(Collectors.toList());
        }
    }

}
